using System;
using System.Collections.Generic;
using System.Text;

using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel;

namespace Athena.Objects
{
    [Serializable]
     class GroupGlyph : IGlyph
    {
        public override object Clone()
        {
            GroupGlyph oResult = new GroupGlyph(_oGroupedElements);
            oResult.Location = Location;
            oResult.Dimensions = Dimensions;
            oResult.Text = Text;
            oResult.LabelBounds = LabelBounds;
            oResult.TextColor = TextColor;
            oResult.TextFont = TextFont;
            oResult.Item =(Athena.Core.Item)_Item.Clone();
            oResult.Type = Type;
            oResult.EdgeColor = EdgeColor;
            oResult.EdgeThickness = EdgeThickness;
            oResult.ColorStart = ColorStart;
            return oResult;
        }

        [Serializable]
        class NodeReference
        {
            public NodeReference(int nIndex, NodeType oType)
            {
                _nIndex = nIndex;
                _oType = oType;
            }

            public NodeReference(Connector c, IDrawable d, NodeType t)
            {
                _oConnector = c;
                _oDrawable = d;
                _oType = t;
            }

            private IDrawable _oDrawable;

            public IDrawable Drawable
            {
                get { return _oDrawable; }
                set { _oDrawable = value; }
            }

            private Connector _oConnector;

            public Connector Connector
            {
                get { return _oConnector; }
                set { _oConnector = value; }
            }


            private int _nIndex;

            public int Index
            {
                get { return _nIndex; }
                set { _nIndex = value; }
            }
            private NodeType _oType;

            public NodeType Type
            {
                get { return _oType; }
                set { _oType = value; }
            }

        }

        private int _nScale = 10;

        [CategoryAttribute("Group Attributes"), Description("This scale factor influences the resolution of the image for the grouped elements.")]
        public int Scale
        {
            get { return _nScale; }
            set { _nScale = value; }
        }
	
        private RectangleF _oOriginalBounds;
        
        [CategoryAttribute("Group Attributes"), Description("The original bounds of the group, that was given when grouping them.")]
        public RectangleF OriginalBounds
        {
            get { return _oOriginalBounds; }
            set { _oOriginalBounds = value; }
        }
	
        enum NodeType { Input, Output };

        private List<IDrawable> _oGroupedElements = new List<IDrawable>();

        [CategoryAttribute("Group Attributes"), Description("List of all elements in this group.")]
        public List<IDrawable> GroupedElements
        {
            get { return _oGroupedElements; }
            set
            {
                _oGroupedElements = value;
                _oOriginalBounds = Utility.GetBounds(value);
                Bounds = new RectangleF(_oOriginalBounds.Location, _oOriginalBounds.Size);
            }
        }
        
        private List<NodeReference> _oNodeReferences = new List<NodeReference>();

        public override bool HasPreview
        {
            get { return false; }
        }

        public override Bitmap GetPreview(int width, int height)
        {
            throw new Exception("The GroupGlyph is not intended to be inserted. Thus no preview is provided.");
        }

        /// <summary>
        /// Creates a new GroupGlyph from the given list of elements, after that the 
        /// elements should be removed from the visual representation. 
        /// </summary>
        /// <param name="oList"></param>
        public GroupGlyph(IEnumerable<IDrawable> oList)
        {
            _oFillColorStart = _oFillColorEnd = Color.DarkGray;
            _oEdgeColor = Color.LightGray;

            _oOriginalBounds = Utility.GetBounds(oList);
            _oOriginalBounds.Inflate(10F, 10F);

            Bounds = new RectangleF(_oOriginalBounds.Location, _oOriginalBounds.Size);

            foreach (IDrawable d in oList)
            {
                if (!(d is BezierConnector))
                {

                d.Location = Utility.SubtractPoints(d.Location ,Bounds.Location);
                    //d.Bounds = RectangleF.Intersect(d.Bounds, Bounds);
                }
                _oGroupedElements.Add(d);
            }

            foreach (IDrawable d in oList)
            {
                if (d is BezierConnector)
                {
                    BezierConnector oCon = d as BezierConnector;
                    oCon.MakePretty();
                }
            }
            
            UpdateConnectors();
        }

        public void UpdateConnectors()
        {
            List<Connector> connectorsToModify = new List<Connector>();

            for (int i = 0; i < _oGroupedElements.Count; ++i)   //for each item 
            {
                if (_oGroupedElements[i] is IGlyph)              //if it is  Glyph
                {
                    IGlyph glyph = (IGlyph)_oGroupedElements[i];

                    if (glyph.OutgoingConnections.Count > 0)
                    {
                        foreach (Connector c in glyph.OutgoingConnections)  //get all its connectors
                        {
                            List<IDrawable> oInputs = c.Reactants;
                            foreach (IDrawable d in oInputs)
                            {
                                if (d == glyph)
                                {
                                    c.AddReactant(this);    //insert this GroupGlyph as a new input
                                    _oNodeReferences.Add(new NodeReference(c, glyph, NodeType.Input));
                                    c.RemoveNode(d);           //and remove the original Glyph from the connector
                                }
                            }
                            connectorsToModify.Add(c);
                        }
                    }
                    if (glyph.IncomingConnections.Count > 0)
                    {
                        foreach (Connector c in glyph.IncomingConnections)  //get all its connectors
                        {
                            List<IDrawable> oInputs = c.Products;
                            foreach (IDrawable d in oInputs)
                            {
                                if (d == glyph)
                                {
                                    c.AddProduct(this);    //insert this GroupGlyph as a new input
                                    _oNodeReferences.Add(new NodeReference(c, glyph, NodeType.Output));
                                    c.RemoveNode(d);           //and remove the original Glyph from the connector
                                }
                            }
                            connectorsToModify.Add(c);
                        }
                    }
                }
            }
            foreach (Connector c in connectorsToModify)
                c.MakePretty();            
        }

        public void RestoreOldConnections()
        {
            List<Connector> connectorsToModify = new List<Connector>();

            foreach (NodeReference oReference in _oNodeReferences)
            {                                
                if (oReference.Type == NodeType.Input)
                    oReference.Connector.AddReactant(oReference.Drawable);
                else
                    oReference.Connector.AddProduct(oReference.Drawable);

                oReference.Connector.RemoveNode(this);
                connectorsToModify.Add(oReference.Connector);
            }

            _oNodeReferences.Clear();

            foreach (Connector c in connectorsToModify)
                c.MakePretty(); 
        }

        NodeReference GetElementFor(int nIndex)
        {
            foreach (NodeReference oRef in _oNodeReferences)
            {
                if (oRef.Index == nIndex)
                    return oRef;
            }
            return null;
        }        

        private bool _bRenderContents = false;

        [CategoryAttribute("Group Attributes"), Description("Should the grouped elements be rendered?")]
        public bool RenderContents
        {
            get { return _bRenderContents; }
            set { _bRenderContents = value; }
        }

        public override PointF Location
        {
            get
            {
                return _Location;
            }
            set
            {
                _Location = value;
            }
        }

        public override SizeF Dimensions
        {
            get
            {
                return _Dimensions;
            }
            set
            {
                _Dimensions = value;
            }
        }

        public override RectangleF Bounds
        {
            get
            {
                return new RectangleF(_Location, _Dimensions);
            }
            set
            {
                _Location = value.Location;
                _Dimensions = value.Size;
            }
        }

        static GraphicsPath _oPath = CreatePath();

        static GraphicsPath CreatePath()
        {
            GraphicsPath oPath = new GraphicsPath();
            Utility.GenerateRoundedRectangle(oPath);
            return oPath;
        }

        public override void RenderGlyph(Graphics g)
        {
            //throw new Exception("The method or operation is not implemented.");
        }

        private void RenderGroupedContentsIntoGraphic(Graphics g, RectangleF pathBounds, Matrix oTransform)
        {
            Bitmap tempBitmap = new Bitmap((int)pathBounds.Width * _nScale, (int)pathBounds.Height * _nScale, g);
            Graphics tempGraphics = Graphics.FromImage(tempBitmap);
            tempGraphics.CompositingQuality = CompositingQuality.HighQuality;
            tempGraphics.SmoothingMode = SmoothingMode.HighQuality;
            tempGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
            oTransform.Translate(_Location.X - _oOriginalBounds.X * pathBounds.Width / _oOriginalBounds.Width, _Location.Y - _oOriginalBounds.Y * _Dimensions.Height / _oOriginalBounds.Height, MatrixOrder.Append);
            tempGraphics.ScaleTransform(pathBounds.Width / _oOriginalBounds.Width * _nScale,
                pathBounds.Height / _oOriginalBounds.Height * _nScale, MatrixOrder.Prepend);

            foreach (IDrawable d in _oGroupedElements)
            {
                d.Draw(tempGraphics);
            }

            g.DrawImage(tempBitmap, Bounds);
        }

        public override void Draw(Graphics g)
        {

            Pen oOutline = new Pen(_oEdgeColor, _dEdgeThickness);
            Brush oFilled;
            if (_oFillColorStart == _oFillColorEnd || _Dimensions.Width < 2 || _Dimensions.Height < 2)
                oFilled = new SolidBrush(_oFillColorStart);
            else
                oFilled = new LinearGradientBrush(Location, new PointF(Location.X + Dimensions.Width, Location.Y + Dimensions.Height), _oFillColorStart, _oFillColorEnd);


            GraphicsPath gp = (GraphicsPath)_oPath.Clone();
            RectangleF pathBounds = gp.GetBounds();

            Matrix oTransform = new Matrix();

            oTransform.Scale(
                _Dimensions.Width / pathBounds.Width,
                _Dimensions.Height / pathBounds.Height, MatrixOrder.Prepend);
            oTransform.Translate(_Location.X - pathBounds.X * _Dimensions.Width / pathBounds.Width, _Location.Y - pathBounds.Y * _Dimensions.Height / pathBounds.Height, MatrixOrder.Append);

            gp.Transform(oTransform);

            //PathGradientBrush oBrush = new PathGradientBrush(gp);
            //oBrush.CenterColor = _oFillColor;
            g.FillPath(Brushes.Beige, gp);                

            if (_bRenderContents)
            {
                // Till now i was rendering the stuff as graphic, which was simple enough as i did not have to 
                // take care of the actuall movement of things. Still alone the scaling issues with the rendered
                // graphic are enough to reconsider the issue ... 
                //
                RenderGroupedContentsIntoGraphic(g, pathBounds, oTransform);
                //

            }

            g.DrawPath(oOutline, gp);

            DrawLabel(g);


            if (_IsSelected)
            {
                RectangleF oBounds = Bounds;

                oBounds.Inflate(2.5F, 2.5F);

                g.DrawRectangle(Pens.Red, oBounds.Location.X, oBounds.Location.Y, oBounds.Size.Width, oBounds.Size.Height);

                g.FillEllipse(Brushes.White, oBounds.Left - 3, oBounds.Top - 3, 6, 6);
                g.DrawEllipse(Pens.Red, oBounds.Left - 3, oBounds.Top - 3, 6, 6);

                g.FillEllipse(Brushes.White, oBounds.Right - 3, oBounds.Top - 3, 6, 6);
                g.DrawEllipse(Pens.Red, oBounds.Right - 3, oBounds.Top - 3, 6, 6);

                g.FillEllipse(Brushes.White, oBounds.Left - 3, oBounds.Bottom - 3, 6, 6);
                g.DrawEllipse(Pens.Red, oBounds.Left - 3, oBounds.Bottom - 3, 6, 6);

                g.FillEllipse(Brushes.White, oBounds.Right - 3, oBounds.Bottom - 3, 6, 6);
                g.DrawEllipse(Pens.Red, oBounds.Right - 3, oBounds.Bottom - 3, 6, 6);

            }

        }
    }
}
